/*
  CocosCreater 3.8.x 2d曲线纹理插件
  可用来制作2d横版的地形,如:山脉,道路等,类似登山赛车和滑雪大冒险的横版平滑的地图
  CocosCreater 3.8.x 2d curve texture plugin
  Can be used to make 2d side-scrolling terrain, such as: mountains, roads, etc., similar to the smooth maps in Hill Climb Racing and Ski Safari
  作者:soida
  Author: soida
  微信:soida3
  WeChat: soida3
  Cocos商店(https://store.cocos.com)也有出售,可直接搜索soida或者关键词'曲线纹理'
  Also available in the Cocos store(https://store.cocos.com), just search  the keyword 'soida' '曲线纹理' 'curve texture'
*/

// import { EDITOR, EDITOR_NOT_IN_PREVIEW } from 'cc/env';

import {
  _decorator,
  Asset,
  AssetManager,
  CCBoolean,
  CCFloat,
  CCInteger,
  Color,
  Component,
  EffectAsset,
  Enum,
  ERigidBody2DType,
  EventKeyboard,
  gfx,
  Input,
  input,
  KeyCode,
  Mat4,
  Material,
  Mesh,
  MeshRenderer,
  misc,
  Node,
  PolygonCollider2D,
  RenderTexture,
  resources,
  RigidBody2D,
  Sprite,
  SpriteFrame,
  sys,
  Texture2D,
  TransformBit,
  UIMeshRenderer,
  UIOpacity,
  UITransform,
  utils,
  v2,
  v3,
  v4,
  Vec2,
  Vec3,
} from 'cc';
import { EDITOR, EDITOR_NOT_IN_PREVIEW } from 'cc/env';
import { MoveWithTouch } from './MoveWithTouch';
const { ccclass, property, executeInEditMode, requireComponent, disallowMultiple, menu, help } = _decorator;

/**
 * 比较两个版本号（格式如 "3.8.0"）
 * @param v1 版本号1
 * @param v2 版本号2
 * @returns
 *   - 正数：v1 > v2
 *   - 负数：v1 < v2
 *   - 0：v1 == v2
 */
function compareVersions(v1: string, v2: string): number {
  // 1. 分割版本号为数字数组
  const parts1 = v1.split('.').map(Number);
  const parts2 = v2.split('.').map(Number);

  // 2. 逐级比较（从左到右）
  for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
    const num1 = parts1[i] || 0; // 缺失的位补0
    const num2 = parts2[i] || 0;
    if (num1 !== num2) {
      return num1 - num2; // 优先级高的位不等时直接返回结果
    }
  }

  // 3. 所有位均相等
  return 0;
}

function getGroupStyleName() {
  if (EDITOR) {
    //@ts-ignore
    return compareVersions(Editor.App.version, '3.8.0') >= 0 ? 'section' : 'tab';
  }
  return 'tab';
}
const GROUP_STYLE_NAME: string = getGroupStyleName();

//兼容低版本未定义
if (EDITOR_NOT_IN_PREVIEW === undefined) {
  console.warn('当前环境无 EDITOR_NOT_IN_PREVIEW 定义,开始兼容性定义');
  const EDITOR_NOT_IN_PREVIEW = EDITOR;
}

//判断Vec2类的原型是否有toVec3()方法,如果没有就添加一个
//@ts-ignore
if (!Vec2.prototype.toVec3) {
  console.warn('当前环境无 Vec2.toVec3() 定义,开始兼容性定义');
  //@ts-ignore
  Vec2.prototype.toVec3 = function (this: Vec2, z: number = 0): Vec3 {
    return new Vec3(this.x, this.y, z);
  };
}

//判断Vec3类的原型是否有toVec2()方法,如果没有就添加一个
//@ts-ignore
if (!Vec3.prototype.toVec2) {
  console.warn('当前环境无 Vec3.toVec2() 定义,开始兼容性定义');
  //@ts-ignore
  Vec3.prototype.toVec2 = function (this: Vec3): Vec2 {
    return new Vec2(this.x, this.y);
  };
}
/**
 *
 * @returns 是否编辑器中文环境,非运行环境 'Whether the editor is in Chinese environment, not running environment'
 */
export function inEditorZH(): boolean {
  if (EDITOR) {
    //@ts-ignore
    return Editor.I18n.getLanguage() === 'zh';
  }
  return false;
}

const IN_EDITOR_ZH: boolean = inEditorZH();

function isCloseToVec3(self: Vec3, other: Vec3, epsilon = 0.00001): boolean {
  const dx = self.x - other.x;
  const dy = self.y - other.y;
  const dz = self.z - other.z;
  const squaredDistance = dx * dx + dy * dy + dz * dz;
  return squaredDistance <= epsilon * epsilon;
}

function isCloseToVec2(self: Vec2, other: Vec2, epsilon = 0.00001): boolean {
  const dx = self.x - other.x;
  const dy = self.y - other.y;
  const squaredDistance = dx * dx + dy * dy;
  return squaredDistance <= epsilon * epsilon;
}

export enum TerrainRenderMode {
  VERTICAL_MODE = 0, //正常竖直模式,按照控制点竖直向下渲染
  TANGENT_MODE = 1, //切线模式,按照控制点的切线垂直方向向下渲染
}

@ccclass('CurveTexture')
@requireComponent(MeshRenderer)
@disallowMultiple
@executeInEditMode
@help('https://github.com/soidaken/CurveTexture2d')
@menu('2D/CurveTexture2d(曲线纹理2d)')
export class CurveTexture extends Component {
  private _curve_width: number = 512;
  private _curve_height: number = 512;
  private _curve_height_result: number = 512;
  private _curve_collider_thickness: number = 0;
  // private _isZH: boolean = sys.language == 'zh';

  private _headNode: Node | null = null;
  private _tailNode: Node | null = null;

  @property({})
  private _syncOnOff: boolean = false;
  @property({
    tooltip: IN_EDITOR_ZH
      ? '自动同步另一个节点的地形数据,开启后此节点无法编辑地形'
      : ' (Automatically sync terrain data from another node, this node cannot edit terrain when enabled)',
    displayName: IN_EDITOR_ZH ? '开启跟随模式' : ' Enable Follow Mode',
  })
  get syncOnOff() {
    return this._syncOnOff;
  }
  set syncOnOff(value) {
    if (!value) {
      this.syncTarget = null;
    }
    this._syncOnOff = value;
    this._refreshAll();
  }

  @property({ type: Node })
  private _syncTarget: Node | null = null;
  @property({
    type: Node,
    tooltip: IN_EDITOR_ZH ? '跟随的目标节点' : ' (Target node to follow)',
    displayName: IN_EDITOR_ZH ? '跟随目标' : ' (Follow Target)',
    // visible() {
    //   return this._syncOnOff;
    // },
  })
  get syncTarget() {
    return this._syncTarget;
  }
  set syncTarget(value) {
    if (this._syncOnOff) {
      if (value) {
        let c = value.getComponent(CurveTexture);
        if (!c) {
          console.warn(
            IN_EDITOR_ZH
              ? '跟随目标节点没有CurveTexture组件,请添加'
              : ' (The target node to follow does not have a CurveTexture component, please add it)'
          );
          return;
        }
        if (value === this.node) {
          console.warn(IN_EDITOR_ZH ? '跟随目标节点不能是自己' : ' (The target node to follow cannot be itself)');
          return;
        }

        //防止循环跟随 'Prevent circular following'
        if (c.syncTarget) {
          if (c.syncTarget === this.node) {
            console.warn(
              IN_EDITOR_ZH ? '出现循环跟随啦,非法操作!' : ' (Circular following detected, illegal operation!)'
            );
            return;
          }
        }
      }
      this._syncTarget = value;

      this._refreshAll();
    } else {
      console.warn(IN_EDITOR_ZH ? '需要先开启跟随模式' : ' (Follow mode needs to be enabled first)');
    }
  }

  @property({ type: Enum(TerrainRenderMode) })
  private _renderMode: TerrainRenderMode = TerrainRenderMode.VERTICAL_MODE;
  @property({
    type: Enum(TerrainRenderMode),
    tooltip: IN_EDITOR_ZH
      ? '渲染模式,VERTICAL_MODE为按照控制点竖直向下渲染,适合厚纹理;TANGENT_MODE为按照控制点的切线垂直方向向下渲染,适合细纹理'
      : ' (Terrain rendering mode, VERTICAL_MODE renders vertically downward according to control points, suitable for thick textures; TANGENT_MODE renders vertically downward according to the tangent direction of control points, suitable for thin textures)',
    displayName: IN_EDITOR_ZH ? '渲染模式' : ' Terrain Rendering Mod',
    group: IN_EDITOR_ZH ? '渲染属性' : '(Terrain Rendering)',
  })
  get renderMode() {
    return this._renderMode;
  }
  set renderMode(value) {
    this._renderMode = value;
    if (value == TerrainRenderMode.TANGENT_MODE) {
      this._thickness = 0;
    }
    this._refreshAll();
  }

  @property({ type: EffectAsset })
  private _spEffectAsset: EffectAsset | null = null;
  @property({
    type: EffectAsset,
    tooltip: IN_EDITOR_ZH ? '渲染effect文件' : 'effect asset',
    displayName: IN_EDITOR_ZH ? '渲染effect文件' : 'effect asset',
    group: IN_EDITOR_ZH ? '渲染属性' : '(Terrain Rendering)',
  })
  get spEffectAsset() {
    return this._spEffectAsset;
  }
  set spEffectAsset(value) {
    this._spEffectAsset = value;

    this._refreshAll();
  }

  private _gizmoInitColor: Color = new Color(255, 255, 255, 255);

  @property({ type: SpriteFrame })
  private _spFrame: SpriteFrame | null = null;
  @property({
    type: SpriteFrame,
    tooltip: IN_EDITOR_ZH ? '渲染纹理' : ' Terrain texture',
    displayName: IN_EDITOR_ZH ? '渲染纹理' : ' Terrain Texture',
    group: IN_EDITOR_ZH ? '渲染属性' : '(Terrain Rendering)',
  })
  get spFrame() {
    return this._spFrame;
  }
  set spFrame(value) {
    this._spFrame = value;
    //取纹理一片区域的颜色均值作为gizmo的颜色
    const uint8arr = RenderTexture.prototype.readPixels.call(value.texture, 0, this._spFrame.height / 2 - 2, 4, 4);
    let r = 0,
      g = 0,
      b = 0;
    for (let i = 0; i < uint8arr.length; i += 4) {
      r += uint8arr[i];
      g += uint8arr[i + 1];
      b += uint8arr[i + 2];
      //忽略alpha通道
    }
    r /= 16;
    g /= 16;
    b /= 16;
    this._gizmoInitColor.set(r, g, b, 255);
    // console.log('this._gizmoInitColor ', this._gizmoInitColor);
    this.gizmoColor = this._gizmoInitColor;
    this._refreshAll();
  }

  @property({})
  private _headTailFixedOnOff: boolean = false;
  @property({
    tooltip: IN_EDITOR_ZH ? '首尾是否有固定纹理' : 'head/tail fixed texture',
    displayName: IN_EDITOR_ZH ? '首尾是否有固定纹理' : 'head/tail fixed texture',
    group: IN_EDITOR_ZH ? '渲染属性' : '(Terrain Rendering)',
    visible(this: CurveTexture) {
      return this._renderMode == TerrainRenderMode.TANGENT_MODE;
    },
  })
  get headTailFixedOnOff() {
    return this._headTailFixedOnOff;
  }
  set headTailFixedOnOff(value) {
    this._headTailFixedOnOff = value;
    this._refreshAll();
  }

  @property({ type: SpriteFrame })
  private _headTailFrame: SpriteFrame | null = null;
  @property({
    type: SpriteFrame,
    tooltip: IN_EDITOR_ZH ? '首尾纹理' : ' Terrain texture',
    displayName: IN_EDITOR_ZH ? '首尾纹理' : ' Terrain Texture',
    group: IN_EDITOR_ZH ? '渲染属性' : '(Terrain Rendering)',
    visible(this: CurveTexture) {
      return this._headTailFixedOnOff == true;
    },
  })
  get headTailFrame() {
    return this._headTailFrame;
  }
  set headTailFrame(value) {
    this._headTailFrame = value;
    this._refreshAll();
  }

  // @property({})
  // private _alphaThreshold: number = 0.4;
  // @property({
  //   tooltip: IN_EDITOR_ZH
  //     ? '如果纹理有透明渲染异常,可以调整这里'
  //     : ' (If there is a transparent rendering issue with the texture, you can adjust here)',
  //   displayName: IN_EDITOR_ZH ? '如果纹理有透明' : ' If the texture has transparency',
  //   group: IN_EDITOR_ZH ? '渲染属性' : '(Terrain Rendering)',
  //   slide: true,
  //   range: [0, 1, 0.01],
  // })
  // get alphaThreshold() {
  //   return this._alphaThreshold;
  // }
  // set alphaThreshold(value) {
  //   this._alphaThreshold = value;
  //   this._refreshAll();
  // }

  @property({})
  private _updownFix: boolean = false;
  @property({
    tooltip: IN_EDITOR_ZH ? '如果上下反了,就来点点我!' : ' (If it is upside down, click me!)',
    displayName: IN_EDITOR_ZH ? '上下反了?' : ' (Upside Down?)',
    group: IN_EDITOR_ZH ? '渲染属性' : ' (Terrain Rendering)',
  })
  get updownFix() {
    return this._updownFix;
  }
  set updownFix(value) {
    this._updownFix = value;
    this._refreshAll();
  }

  @property({})
  private _reverseHeadTail: boolean = false;
  @property({
    displayName: IN_EDITOR_ZH ? '翻转首尾?' : ' (Head - Tail Reverse?)',
    group: IN_EDITOR_ZH ? '渲染属性' : ' (Terrain Rendering)',
  })
  get reverseHeadTail() {
    return this._reverseHeadTail;
  }
  set reverseHeadTail(value) {
    this._reverseHeadTail = value;
    this._refreshAll();
  }


  @property({})
  private _manualControlPoints: boolean = false;
  @property({
    displayName: IN_EDITOR_ZH ? '手动控制点?' : ' (manual control points)',
    group: IN_EDITOR_ZH ? '渲染属性' : ' (Terrain Rendering)',
  })
  get manualControlPoints() {
    return this._manualControlPoints;
  }
  set manualControlPoints(value) {
    this._manualControlPoints = value;
    this._refreshAll();
  }

  @property({})
  private _thickness: number = 0;
  @property({
    tooltip: IN_EDITOR_ZH
      ? '地形的厚度,填0是设置为纹理高度值'
      : ' (Thickness of the terrain, 0 sets it to the texture height value)',
    displayName: IN_EDITOR_ZH ? '渲染厚度' : ' (Terrain Thickness)',
    group: IN_EDITOR_ZH ? '渲染属性' : ' (Terrain Rendering)',
    visible(this: CurveTexture) {
      return this._renderMode == TerrainRenderMode.VERTICAL_MODE;
    },
  })
  get thickness() {
    return this._thickness;
  }
  set thickness(value) {
    this._thickness = value;
    this._refreshAll();
  }

  @property({})
  private _offset: Vec3 = v3(0, 0, 0);
  @property({
    type: Vec3,
    tooltip: IN_EDITOR_ZH
      ? '渲染起始的偏移值,z暂时无效'
      : ' (Offset value for rendering start, z is temporarily invalid)',
    group: IN_EDITOR_ZH ? '渲染属性' : ' (Terrain Rendering)',
    displayName: IN_EDITOR_ZH ? '渲染偏移' : ' (Rendering Offset)',
  })
  get offset() {
    return this._offset;
  }
  set offset(value) {
    this._offset = value;
    this._offset.z = 0;
    this._refreshAll();
  }

  // @property({})
  public _controlPoints: Vec3[] = [v3(-500, -130, 0), v3(-160, 30, 0), v3(160, -130, 0), v3(500, 30, 0)];
  get controlPoints() {
    return this._controlPoints;
  }
  set controlPoints(value) {
    this._controlPoints = null;
    this._controlPoints = value.slice();
    this._refreshAll();
  }

  @property({})
  private _smoothness: number = 16;
  @property({
    type: CCInteger,
    tooltip: IN_EDITOR_ZH
      ? '每两控制点之间细分的数量,默认16'
      : ' (Number of subdivisions between every two control points, default is 16)',
    displayName: IN_EDITOR_ZH ? '细分点数量' : ' (Number of Subdivided Points)',
    group: IN_EDITOR_ZH ? '渲染属性' : ' (Terrain Rendering)',
    slide: true,
    range: [4, 32, 1],
  })
  get smoothness() {
    return this._smoothness;
  }
  set smoothness(value) {
    this._smoothness = value;
    this._refreshAll();
  }

  @property({})
  private _xPanSpeed: number = 0;
  @property({
    type: CCFloat,
    tooltip: IN_EDITOR_ZH
      ? '贴图会随着时间在绳子方向上平移'
      : ' (The texture will shift along the rope direction over time)',
    displayName: IN_EDITOR_ZH ? 'x轴的平移动画' : ' (axis-x pan animation)',
    group: IN_EDITOR_ZH ? '渲染属性' : ' (Terrain Rendering)',
    slide: true,
    range: [-4, 4, 1],
  })
  get xPanSpeed() {
    return this._xPanSpeed;
  }
  set xPanSpeed(value) {
    this._xPanSpeed = value;
    this._refreshAll();
  }

  @property({})
  private _physcisOnOff: boolean = false;
  @property({
    tooltip: IN_EDITOR_ZH
      ? '在一个特定子节点上添加[PhysicsPolygonCollider]组件,你也可以通过[getCurveTexturePoints] 获取数据后自己实现.'
      : 'Add a [PhysicsPolygonCollider] component to a specific child node, or you can implement it yourself after fetching data from [getCurveTexturePoints].)',
    displayName: IN_EDITOR_ZH ? '同步生成物理属性' : ' (Synchronize Generation of Physical Properties)',
    group: IN_EDITOR_ZH ? '物理属性' : ' (Physical Properties)',
  })
  get physcisOnOff() {
    return this._physcisOnOff;
  }
  set physcisOnOff(value) {
    this._physcisOnOff = value;

    this._refreshAll();
  }

  @property({})
  private _physicsThickness: number = 0;
  @property({
    type: CCFloat,
    tooltip: IN_EDITOR_ZH
      ? '物理碰撞器的厚度,填0是设置为纹理高度值'
      : ' (Thickness of the physical collider, 0 sets it to the texture height value)',
    displayName: IN_EDITOR_ZH ? '物理碰撞器厚度' : ' (Physical Collider Thickness)',
    group: IN_EDITOR_ZH ? '物理属性' : ' (Physical Properties)',
    visible(this: CurveTexture) {
      return this._physcisOnOff;
    },
  })
  get physicsThickness() {
    return this._physicsThickness;
  }
  set physicsThickness(value) {
    this._physicsThickness = value;
    this._refreshAll();
  }

  private _physicsCollider: PolygonCollider2D | null = null;

  //获取对应的物理碰撞器 'Get the corresponding physical collider'
  public get physicsCollider() {
    return this._physicsCollider;
  }

  @property({})
  private _offsetCollider: Vec3 = v3(0, 0, 0);
  @property({
    type: Vec3,
    tooltip: IN_EDITOR_ZH
      ? '物理碰撞器的偏移值,默认(0,0,0)'
      : ' (Offset value for the physical collider, default is (0,0,0))',
    displayName: IN_EDITOR_ZH ? '物理碰撞器偏移' : ' (Physical Collider Offset)',
    group: IN_EDITOR_ZH ? '物理属性' : ' (Physical Properties)',
    visible(this: CurveTexture) {
      return this._physcisOnOff;
    },
  })
  get offsetCollider() {
    return this._offsetCollider;
  }
  set offsetCollider(value) {
    this._offsetCollider = value;
    this._offsetCollider.z = 0;
    this._refreshAll();
    // if (this._physicsCollider) {
    //   this._physicsCollider.offset = this._offsetCollider.clone();
    // }
  }

  /**
   *
   * @returns 获取细分后的所有数据点,相对于当前节点坐标系 'Get all subdivided data points, relative to the current node coordinate system'
   */
  public getCurveTexturePoints(): Vec3[] {
    return this._vertexesSegment;
  }

  //
  // private _chainCollider: PhysicsChainCollider = null;

  //将编辑点 _controlPoints 细分后的平滑渲染点 'Smooth rendering points after subdividing the editing points _controlPoints'
  private _vertexesSegment: Vec3[] = [];
  /**
   * 获取细分后的所有数据点,相对于当前节点坐标系 'Get all subdivided data points, relative to the current node coordinate system'
   */
  get vertexesSegment() {
    return this._vertexesSegment;
  }

  //最终的渲染顶点数据 'Final rendering vertex data'
  private _vertexes: Vec3[] = [];
  // get vertexes() {
  //   return this._vertexes;
  // }
  // set vertexes(value) {
  //   this._controlPoints = value.slice();
  //   this._refreshAll();
  // }

  public renderer: MeshRenderer | null = null;

  protected onLoad() {
    let tmp: Vec3[] = [];
    for (let i = 0; i < this.node.children.length; i++) {
      const item = this.node.children[i];
      if (item.name.startsWith('gizmo_')) {
        tmp.push(item.position.clone());
      }
    }
    // console.log(`${this.node.name} onLoad 设置了编辑点 _controlPoints 个数: `, tmp.length);
    this._controlPoints = tmp.length > 0 ? tmp : this._controlPoints;
  }

  /**
   * 打印调试信息 'Print debug information'
   * _curve_width: 纹理宽度 'Texture width'
   * _curve_height: 纹理高度 'Texture height'
   * _controlPoints: 编辑点 'Editing points'
   * _vertexesSegment: 细分点 'Subdivided points'
   * _vertexes: 渲染点 'Rendering points'
   */
  debugPrint() {
    console.log(this.node.name + ' CurveTexture.ts debugPrint _curve_width : ', this._curve_width);
    console.log(this.node.name + ' CurveTexture.ts debugPrint _curve_height : ', this._curve_height);
    console.log(this.node.name + ' CurveTexture.ts debugPrint _controlPoints : ', this._controlPoints);
    console.log(this.node.name + ' CurveTexture.ts debugPrint _vertexesSegment : ', this._vertexesSegment);
    console.log(this.node.name + ' CurveTexture.ts debugPrint _vertexes : ', this._vertexes);
  }

  protected onEnable() {
    this.node.off(Node.EventType.TRANSFORM_CHANGED);
    this.node.on(
      Node.EventType.TRANSFORM_CHANGED,
      (type: TransformBit) => {
        if (type & Node.TransformBit.SCALE) {
          console.log('CurveTexture.ts scale changed, 刷新一次' + ' (refresh once)');
          this._refreshAll('559');
        }
      },
      this
    );
    this._registerKeyForGizmo();
    this._refreshAll('565');
  }

  public _refreshAll(from: string = '') {
    // console.log(`from ${this.node.name} ${from}`);
    let renderer = this.node.getComponent(MeshRenderer);
    if (!renderer) {
      console.warn('MeshRenderer component not found!');
      return;
    }
    this.renderer = renderer;

    //强制转换2d渲染模式,使用2d的层级管理
    let uiMeshR = this.node.getComponent(UIMeshRenderer);
    if (!uiMeshR) {
      this.node.addComponent(UIMeshRenderer);
    }

    if (!this.renderer) {
      console.warn(
        IN_EDITOR_ZH
          ? '没有MeshRenderer组件,请添加MeshRenderer组件!'
          : ' (No MeshRenderer component, please add MeshRenderer component!)'
      );
      return;
    }

    let csame = this.node.getComponents(CurveTexture);
    if (csame.length > 1) {
      console.error(
        this.node.name + IN_EDITOR_ZH
          ? ' 请去除无用的[ CurveTexture ]组件,一个节点只需绑定一个,多个存在只会有最后一个生效!'
          : ' (Please remove unnecessary [CurveTexture] components, only one needs to be bound to a node, if multiple exist, only the last one will take effect!)'
      );
    }

    if (!this._spFrame) {
      console.warn(
        IN_EDITOR_ZH
          ? '还未指定 [ 渲染纹理 ],拖入插件默认的 [ terrain-ground ] 贴图即可!'
          : ' (The [Terrain Texture] has not been specified yet, drag in the default [terrain-ground] texture of the plugin!)'
      );
      return;
    }

    if (!this._spEffectAsset) {
      console.warn(
        IN_EDITOR_ZH
          ? '还未指定 [ 渲染effect文件 ],拖入插件默认的 [ curvetexure-sprite.effect ] effect文件即可!'
          : ' (The [Effect Asset] has not been specified yet, drag in the default [curvetexure-sprite.effect] texture of the plugin!)'
      );
      return;
    }

    this._curve_height = this._spFrame.originalSize.height; //纹理高度 'Texture height'
    this._curve_width = this._spFrame.originalSize.width;

    //检查纹理的宽和高是否是2的指数 'Check if the width and height of the texture are powers of 2'
    if (this._curve_width & (this._curve_width - 1)) {
      console.warn(
        IN_EDITOR_ZH
          ? '纹理的宽度不是2的指数,请检查!'
          : ' (The width of the texture is not a power of 2, please check!)'
      );
      return;
    }
    if (this._curve_height & (this._curve_height - 1)) {
      console.warn(
        IN_EDITOR_ZH
          ? '纹理的高度不是2的指数,请检查!'
          : ' (The height of the texture is not a power of 2, please check!)'
      );
      return;
    }

    if (this.gizmoTag == null) {
      console.warn(
        IN_EDITOR_ZH
          ? '还未指定[ 控制点纹理 ]! 拖入 [ gizmotag ] 资源即可'
          : ' ([ Control Point Texture] has not been specified yet! Drag in the [gizmotag] resource)'
      );
      // return;
    }

    // if (this.gizmoTagLine == null) {
    //   console.warn(
    //     IN_EDITOR_ZH
    //       ? '还未指定[ 控制点连接纹理 ]! 拖入 [ gizmotagline ] 资源即可'
    //       : ' ([ Control Line Texture] has not been specified yet! Drag in the [gizmotagline] resource)'
    //   );
    //   // return;
    // }

    let c = this.node.getComponent(UIOpacity);
    if (c) {
      console.warn(
        this.node.name +
        (IN_EDITOR_ZH
          ? ' 请去除[ UIOpacity ]组件,实际渲染忽略这个属性,如果需要透明请修改原始贴图!'
          : ' (Please remove the [UIOpacity] component, this property is ignored in actual rendering, if transparency is needed, please modify the original texture!)')
      );
    }

    this._applySpriteFrame();
    if (this._syncOnOff && this._syncTarget) {
      //同步模式下不自身生成细分点 'In sync mode, do not generate subdivided points by itself'
    } else {
      this._generateSegmentVertex(this._smoothness);
      this._updateHeadTail();
    }

    if (this._vertexesSegment.length > 0) {
      this._updateMesh();
      this.generatePhysicData();
    }
  }

  private _applySpriteFrame() {
    if (this._spFrame && this.renderer) {
      this._curve_height_result = this._thickness == 0 ? this._curve_height : this._thickness;
      this._curve_collider_thickness = this._physicsThickness == 0 ? this._curve_height : this._physicsThickness;
      let texture = this._spFrame.texture;
      texture.setWrapMode(Texture2D.WrapMode.REPEAT, Texture2D.WrapMode.REPEAT, Texture2D.WrapMode.REPEAT);
      let mat = this.renderer.getMaterialInstance(0) as Material;

      if (!mat || mat.effectAsset !== this._spEffectAsset) {
        mat = new Material();
        mat.initialize({
          effectAsset: this._spEffectAsset,
          defines: {
            USE_TEXTURE: true,
          },
        });
        this.renderer.setMaterialInstance(mat, 0);
      }
      mat.setProperty('mainTexture', texture, 0);
      const uv = this._spFrame.uv;
      const uv_x = uv[0]; // ul
      const uv_y = uv[1]; // vb
      const uv_w = uv[6] - uv[0]; // ur - ul
      const uv_h = uv[5] - uv[1]; // vt - vb
      const altlasUV = v4(uv_x, uv_y, uv_w, uv_h);
      // console.log(this.node.name + ' altlasUV', altlasUV);
      mat.setProperty('altlasUV', altlasUV, 0);

      mat.setProperty('params', v4(this.xPanSpeed, 0, 0, 0));

    } else {
      if (!this._spFrame) {
        let mat = this.renderer!.getMaterialInstance(0) as Material;
        mat.setProperty('mainTexture', null, 0);
      }
    }
  }

  private _getTangentPos(current: Vec3, next: Vec3, angleRad: number, thickness: number): Vec3 {
    const v1 = next
      .clone()
      .subtract(current)
      .normalize()
      .toVec2()
      .rotate(angleRad)
      .multiplyScalar(thickness)
      .add(current.toVec2());
    return v1.toVec3();
  }

  private _updateHeadTail() {
    this._updateHead();
    this._updateTail();
  }

  private _transfromPositonWhenScale(inpos: Vec3, targetNode: Node) {
    const localPos = inpos.clone();
    const worldMat = targetNode.getWorldMatrix();
    const worldPos = new Vec3();
    Vec3.transformMat4(worldPos, localPos, worldMat);
    const invParentMat = new Mat4();
    Mat4.invert(invParentMat, targetNode.getWorldMatrix());
    const newLocalPos = new Vec3();
    Vec3.transformMat4(newLocalPos, worldPos, invParentMat);
    return newLocalPos;
  }

  private _updateHead() {
    if (this._headTailFixedOnOff) {
      if (!this._headTailFrame) {
        console.warn(IN_EDITOR_ZH ? '请指定对应的首尾纹理' : ' (Please set the head/tail texture)');
        return;
      }
      let headNode = this.node.getChildByName('_headNode_');
      if (!headNode) {
        headNode = new Node('_headNode_');
        headNode.setParent(this.node);
      }
      headNode.setSiblingIndex(0);
      headNode.setPosition(0, 0, this.node.z);
      // headNode.setScale(this.node.scale.x, this.node.scale.y, 1);
      let meshR = headNode.getComponent(MeshRenderer);
      if (!meshR) {
        meshR = headNode.addComponent(MeshRenderer);
      }
      let uiMeshR = this.node.getComponent(UIMeshRenderer);
      if (!uiMeshR) {
        headNode.getComponent(MeshRenderer)?.destroy();
      } else {
        if (!headNode.getComponent(UIMeshRenderer)) {
          headNode.addComponent(UIMeshRenderer);
        }
      }

      let mat = meshR.getMaterialInstance(0) as Material;
      if (!mat) {
        mat = new Material();
        mat.initialize({
          effectAsset: this._spEffectAsset,
          defines: {
            USE_TEXTURE: true,
          },
        });
        meshR.setMaterialInstance(mat, 0);
      }
      mat.setProperty('mainTexture', this._headTailFrame.texture, 0);
      const uv = this._headTailFrame.uv;
      const uv_x = uv[0]; // ul
      const uv_y = uv[1]; // vb
      const uv_w = uv[6] - uv[0]; // ur - ul
      const uv_h = uv[5] - uv[1]; // vt - vb
      const altlasUV = v4(uv_x, uv_y, uv_w, uv_h);
      // console.log('altlasUV', altlasUV);
      mat.setProperty('altlasUV', altlasUV, 0);

      const cur = this._vertexesSegment[0]; //.clone().multiply(this.node.scale);
      const next = this._vertexesSegment[1]; //.clone().multiply(this.node.scale);

      const p2 = cur.clone();
      const p3 = this._getTangentPos(p2, next, -Math.PI / 2, this._curve_height);
      const p0 = cur.clone().subtract(next).normalize().multiplyScalar(this._headTailFrame.originalSize.width).add(cur);
      const p1 = p3.clone().subtract(p2).add(p0);
      let posset: number[] = [];
      let tmp: Vec3[] = [];
      tmp.push(p0, p1, p2, p3);
      for (let i = 0; i < tmp.length; i++) {
        posset.push(tmp[i].x, tmp[i].y, tmp[i].z);
      }
      let uvs = [0, 0, 0, 1, 1, 0, 1, 1];
      let indices = this.node.scale.x * this.node.scale.y > 0 ? [0, 1, 2, 1, 3, 2] : [0, 2, 1, 1, 2, 3];
      // let indices = [0, 1, 2, 1, 3, 2];
      if (headNode.scale.x * headNode.scale.y < 0) {
        indices = [0, 2, 1, 1, 2, 3];
      }
      let mesh = utils.MeshUtils.createMesh({
        positions: posset,
        uvs: uvs,
        indices: indices,
      });
      if (meshR!.mesh) {
        meshR.mesh!.destroy();
      }
      meshR.mesh = mesh;
    } else {
      let headNode = this.node.getChildByName('_headNode_');
      if (headNode) {
        headNode.destroy();
      }
    }
  }
  private _updateTail() {
    if (this._headTailFixedOnOff) {
      if (!this._headTailFrame) {
        console.warn(IN_EDITOR_ZH ? '请指定对应的首尾纹理' : ' (Please set the head/tail texture)');
        return;
      }
      let tailNode = this.node.getChildByName('_tailNode_');
      if (!tailNode) {
        tailNode = new Node('_tailNode_');
        tailNode.setParent(this.node);
      }
      tailNode.setSiblingIndex(0);
      tailNode.setPosition(0, 0, this.node.z);
      // tailNode.setScale(this.node.scale.x, this.node.scale.y, 1);
      let meshR = tailNode.getComponent(MeshRenderer);
      if (!meshR) {
        meshR = tailNode.addComponent(MeshRenderer);
      }
      let uiMeshR = this.node.getComponent(UIMeshRenderer);
      if (!uiMeshR) {
        tailNode.getComponent(MeshRenderer)?.destroy();
      } else {
        if (!tailNode.getComponent(UIMeshRenderer)) {
          tailNode.addComponent(UIMeshRenderer);
        }
      }
      let mat = meshR.getMaterialInstance(0) as Material;
      if (!mat) {
        mat = new Material();
        mat.initialize({
          effectAsset: this._spEffectAsset,
          defines: {
            USE_TEXTURE: true,
          },
        });
        // this._headTailFrame.texture.setWrapMode(
        //   Texture2D.WrapMode.REPEAT,
        //   Texture2D.WrapMode.REPEAT,
        //   Texture2D.WrapMode.REPEAT
        // );
        meshR.setMaterialInstance(mat, 0);
      }
      mat.setProperty('mainTexture', this._headTailFrame.texture, 0);
      const uv = this._headTailFrame.uv;
      const uv_x = uv[0]; // ul
      const uv_y = uv[1]; // vb
      const uv_w = uv[6] - uv[0]; // ur - ul
      const uv_h = uv[5] - uv[1]; // vt - vb
      const altlasUV = v4(uv_x, uv_y, uv_w, uv_h);
      // console.log('altlasUV', altlasUV);
      mat.setProperty('altlasUV', altlasUV, 0);

      const p0 = this._vertexesSegment[this._vertexesSegment.length - 1].clone();
      const p1 = this._getTangentPos(
        p0,
        this._vertexesSegment[this._vertexesSegment.length - 2],
        Math.PI / 2,
        this._curve_height
      );
      const p2 = p0
        .clone()
        .subtract(this._vertexesSegment[this._vertexesSegment.length - 2])
        .normalize()
        .multiplyScalar(this._headTailFrame.originalSize.width)
        .add(p0);
      const p3 = p2.clone().subtract(p0).add(p1);
      let posset: number[] = [];
      let tmp: Vec3[] = [];
      tmp.push(p0, p1, p2, p3);
      for (let i = 0; i < tmp.length; i++) {
        posset.push(tmp[i].x, tmp[i].y, tmp[i].z);
      }
      let uvs = [1, 0, 1, 1, 0, 0, 0, 1];
      let indices = this.node.scale.x * this.node.scale.y > 0 ? [0, 1, 2, 1, 3, 2] : [0, 2, 1, 1, 2, 3];
      let mesh = utils.MeshUtils.createMesh({
        positions: posset,
        uvs: uvs,
        indices: indices,
      });
      if (meshR!.mesh) {
        meshR.mesh!.destroy();
      }
      meshR.mesh = mesh;
    } else {
      let headNode = this.node.getChildByName('_tailNode_');
      if (headNode) {
        headNode.destroy();
      }
    }
  }
  //细分控制点,通过Catmull-Rom 样条曲线来插值平滑 'Subdivide control points, interpolate and smooth using Catmull-Rom spline'
  private _catmullRom(p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, t: number): Vec3 {
    const t2 = t * t;
    const t3 = t2 * t;

    // Catmull-Rom spline formula for 3D
    const x =
      0.5 *
      (2 * p1.x +
        (-p0.x + p2.x) * t +
        (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 +
        (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3);
    const y =
      0.5 *
      (2 * p1.y +
        (-p0.y + p2.y) * t +
        (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +
        (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3);
    // const z =
    //   0.5 *
    //   (2 * p1.z +
    //     (-p0.z + p2.z) * t +
    //     (2 * p0.z - 5 * p1.z + 4 * p2.z - p3.z) * t2 +
    //     (-p0.z + 3 * p1.z - 3 * p2.z + p3.z) * t3);

    return v3(x, y, 0);
  }

  //最小分段距离 'Minimum segment length'
  private _minSegmentLength: number = 8;
  private _generateSegmentVertex(seg_count: number = 8) {
    this._vertexesSegment.length = 0;
    const points = this._controlPoints;
    const pointCount = points.length;

    if (pointCount < 2) {
      if (pointCount === 1) {
        this._vertexesSegment.push(points[0].clone());
      }
      return;
    }

    if (pointCount === 2) {
      const p0 = points[0];
      const p1 = points[1];
      const dist = p0.clone().subtract(p1).length();
      let seg = 1; // seg_count;
      // 动态调整分段数 'Dynamically adjust the number of segments'
      while (seg > 1 && dist / seg < this._minSegmentLength) {
        seg = Math.floor(seg / 2);
      }
      this._vertexesSegment.push(p0.clone());
      const step = 1 / seg;
      for (let i = 1; i < seg; i++) {
        const t = i * step;
        this._vertexesSegment.push(p0.clone().lerp(p1, t));
      }
      this._vertexesSegment.push(p1.clone());
      return;
    }

    // Catmull-Rom样条 'Catmull-Rom spline'
    const paddedPoints: Vec3[] = [];
    paddedPoints.push(points[0].clone().add(points[0].clone().subtract(points[1])));
    paddedPoints.push(...points);
    paddedPoints.push(
      points[pointCount - 1].clone().add(points[pointCount - 1].clone().subtract(points[pointCount - 2]))
    );

    for (let i = 0; i < pointCount - 1; i++) {
      const p0 = paddedPoints[i];
      const p1 = paddedPoints[i + 1];
      const p2 = paddedPoints[i + 2];
      const p3 = paddedPoints[i + 3];

      if (i === 0) {
        this._vertexesSegment.push(p1.clone());
      }

      // 计算该段的长度（用p1-p2的距离近似） 'Calculate the length of this segment (approximated by the distance between p1 and p2)'
      const dist = p1.clone().subtract(p2).length();
      let seg = seg_count;
      // 如果距离小于最小分段距离，则不分段，直接连接
      if (dist < 32) {
        seg = 1;
      } else {
        while (seg > 1 && dist / seg < this._minSegmentLength) {
          seg = Math.floor(seg / 2);
        }
      }

      const step = 1 / seg;
      for (let j = 1; j <= seg; j++) {
        const t = j * step;
        const interpolatedPoint = this._catmullRom(p0, p1, p2, p3, t);
        this._vertexesSegment.push(interpolatedPoint);
      }
    }

    if (this._vertexesSegment.length > 0) {
      const lastOriginalPoint = points[pointCount - 1];
      this._vertexesSegment[this._vertexesSegment.length - 1] = lastOriginalPoint.clone();
    }

    // 需要在不同模式下分别考虑
    for (let i = this._vertexesSegment.length - 1; i > 0; i--) {
      // if (this._vertexesSegment[i].equals(this._vertexesSegment[i - 1], 2)) {
      if (isCloseToVec3(this._vertexesSegment[i], this._vertexesSegment[i - 1], 2)) {
        this._vertexesSegment.splice(i, 1);
      }
    }

    this._vertexesSegment = this._vertexesSegment.filter(
      (v) => isFinite(v.x) && isFinite(v.y) && (v.x !== 0 || v.y !== 0)
    );

    // console.log(
    //   this.node.name + (IN_EDITOR_ZH ? ' 细分后点数量为:' : ' (Number of points after subdivision):'),
    //   this._vertexesSegment.length
    // );
  }

  private _updateMesh() {
    if (!this._spFrame || this._controlPoints.length < 2) {
      // console.warn(IN_EDITOR_ZH ? '至少需要两个控制点' : 'Need at least 2 points to define the curve.'); // remove warn
      this.renderer!.mesh = null;
      return;
    }

    const positions = this._calculateVertexes();
    if (positions.length === 0) {
      // console.warn(this.node.name, IN_EDITOR_ZH ? '顶点数据数量为0' : 'No vertexes calculated.');// remove warn
      this.renderer!.mesh = null;
      return;
    }

    const uvs =
      this.renderMode == TerrainRenderMode.VERTICAL_MODE ? this._calculateUVs() : this._calculateTangentsUVs();
    // const uvs = this._calculateUVs();
    const indices = this._calculateIndices();
    // const normals = this._calculateNormals();

    // console.log('CurveTexture.ts _updateMesh pos  ', positions);
    // console.log('CurveTexture.ts _updateMesh uvs  ', uvs);
    // console.log('CurveTexture.ts _updateMesh indices  ', indices);
    // console.log('CurveTexture.ts _updateMesh normals  ', normals);

    let mesh = utils.MeshUtils.createMesh({
      positions: positions,
      uvs: uvs,
      indices: indices,
      // normals: normals,
    });

    // console.log('CurveTexture.ts _updateMesh positions ', positions);
    // console.log('CurveTexture.ts _updateMesh uvs ', uvs);
    // console.log('CurveTexture.ts _updateMesh indices ', indices);

    if (this.renderer!.mesh) {
      this.renderer!.mesh.destroy();
    }
    this.renderer!.mesh = mesh;
  }

  private _crossPointBy2Segment(p1: Vec2, p2: Vec2, p3: Vec2, p4: Vec2): Vec2 | false {
    const a = p1,
      b = p2,
      c = p3,
      d = p4;

    // 计算分母 'Calculate denominator'
    const denominator = (d.y - c.y) * (b.x - a.x) - (d.x - c.x) * (b.y - a.y);
    if (denominator === 0) return false; // 平行或共线 'Parallel or collinear'

    // 计算 ua 和 ub 'Calculate ua and ub'
    const ua = ((d.x - c.x) * (a.y - c.y) - (d.y - c.y) * (a.x - c.x)) / denominator;
    const ub = ((b.x - a.x) * (a.y - c.y) - (b.y - a.y) * (a.x - c.x)) / denominator;

    // 检查交点是否在线段上 'Check if the intersection point is on the segment'
    if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
      return v2(a.x + ua * (b.x - a.x), a.y + ua * (b.y - a.y));
    }

    return false;
  }

  // 求分别过两点的直线的交点
  private _crossPointBy2Line(p1: Vec2, p2: Vec2, p3: Vec2, p4: Vec2): Vec2 | false {
    const a = p1, b = p2, c = p3, d = p4;

    // 计算分母（两直线方向向量的叉积）
    const denominator = (d.y - c.y) * (b.x - a.x) - (d.x - c.x) * (b.y - a.y);
    if (denominator === 0) return false; // 平行或重合

    // 计算参数ua（不需要检查范围）
    const ua = ((d.x - c.x) * (a.y - c.y) - (d.y - c.y) * (a.x - c.x)) / denominator;

    // 返回交点坐标（基于第一条直线的参数方程）
    return v2(a.x + ua * (b.x - a.x), a.y + ua * (b.y - a.y));
  }


  /**
   * 计算从向量a到向量b的有符号夹角
   * @param a 起始向量
   * @param b 目标向量
   * @param normal 参考法向量（决定正方向）,默认(0,0,1)
   * @returns 夹角（范围：-π ~ π），零向量返回NaN
   */
  private _signedAngle3D(a: Vec3, b: Vec3, normal?: Vec3): number {
    // 检查零向量
    const magA = Math.sqrt(a.x ** 2 + a.y ** 2 + a.z ** 2);
    const magB = Math.sqrt(b.x ** 2 + b.y ** 2 + b.z ** 2);
    if (magA === 0 || magB === 0) return NaN;

    // 计算点积和叉积
    const dot = a.x * b.x + a.y * b.y + a.z * b.z;
    const cross = {
      x: a.y * b.z - a.z * b.y,
      y: a.z * b.x - a.x * b.z,
      z: a.x * b.y - a.y * b.x,
    };
    const nor = normal ? normal : v3(0, 0, 1);
    // 叉积与法向量的点积决定方向
    const crossDotNormal = cross.x * nor.x + cross.y * nor.y + cross.z * nor.z;
    const sign = Math.sign(crossDotNormal);

    // 计算无符号夹角
    const cosTheta = Math.min(1, Math.max(-1, dot / (magA * magB)));
    const unsignedAngle = Math.acos(cosTheta);

    return sign * unsignedAngle;
  }

  private _calculateVertexes() {
    let positions: number[] = [];
    this._vertexes.length = 0;

    // 测试代码,需要删除-------------start 'Test code, needs to be deleted-------------start'

    // this._vertexesSegment.length = 0;
    // this._controlPoints.forEach((element) => {
    //   this._vertexesSegment.push(element.clone());
    // });

    // 测试代码,需要删除-------------end 'Test code, needs to be deleted-------------end'

    if (this._vertexesSegment.length < 2) {
      console.warn(this.node.name, IN_EDITOR_ZH ? '细分点数量小于2' : 'count of subdivided points is less than 2');
      return positions;
    }

    let temp: Vec3[] = [];
    for (let i = 0; i < this._vertexesSegment.length; i++) {
      const v = this._vertexesSegment[i];
      if (v) {
        temp.push(v.clone().add(this._offset));
      }
    }
    if (this._renderMode == TerrainRenderMode.VERTICAL_MODE) {
      for (let index = 0; index < temp.length - 1; index++) {
        const item1 = temp[index];
        const item2 = temp[index + 1];
        const item1_bottom = item1.clone().add(v3(0, -this._curve_height_result, 0));
        const item2_bottom = item2.clone().add(v3(0, -this._curve_height_result, 0));

        this._vertexes.push(item1);
        this._vertexes.push(item1_bottom);
        this._vertexes.push(item2);

        this._vertexes.push(item1_bottom);
        this._vertexes.push(item2_bottom);
        this._vertexes.push(item2);
      }
    } else if (this._renderMode == TerrainRenderMode.TANGENT_MODE) {
      for (let index = 0; index < temp.length - 1; index++) {
        const t_current = temp[index];
        const A = t_current;
        //不应该是直接垂直向下.而应该是垂直于切线. 'Should not be directly vertical downward, but perpendicular to the tangent.'
        //已知AB两点, 计算点C坐标,AC垂直于AB且AC长度为L 'Given points A and B, calculate the coordinates of point C, AC is perpendicular to AB and AC has a length of L'

        /*
        if (index == temp.length - 1) {
          this._vertexes.push(t_current);
          break;
        }
        */

        const t_next = temp[index + 1];
        const B = t_next;
        const len = A.clone().subtract(B).length();
        const sinValue = (B.y - A.y) / len;
        const cosValue = (B.x - A.x) / len;
        const CX = A.x + this._curve_height * sinValue / 2;
        const CY = A.y - this._curve_height * cosValue / 2;

        const DX = A.x - this._curve_height * sinValue / 2;
        const DY = A.y + this._curve_height * cosValue / 2;

        const first_vertical_pos = v2(CX, CY);
        const second_vertical_pos = v2(CX, CY).add(B.clone().subtract(A).toVec2());

        const third_vertical_pos = v2(DX, DY);
        const fourth_vertical_pos = v2(DX, DY).add(B.clone().subtract(A).toVec2());

        // Ethan: 计算连接点

        if (index == 0) {
          this._vertexes.push(third_vertical_pos.toVec3());
          this._vertexes.push(first_vertical_pos.toVec3());
          this._vertexes.push(fourth_vertical_pos.toVec3());
          this._vertexes.push(second_vertical_pos.toVec3());
        } else {
          let last_2nd = this._vertexes.pop();
          let last_4th = this._vertexes.pop();
          let last_1st = this._vertexes[this._vertexes.length - 1];
          let last_3rd = this._vertexes[this._vertexes.length - 2];

          let connect_l = this._crossPointBy2Line(last_3rd.toVec2(), last_4th.toVec2(), third_vertical_pos, fourth_vertical_pos);
          let connect_r = this._crossPointBy2Line(last_1st.toVec2(), last_2nd.toVec2(), first_vertical_pos, second_vertical_pos);

          // 防止控制点反折，形成小锐角，平行线焦点有可能出现在很远的地方，从而出现尖刺。
          // 优化思路：当尖刺出现大于根号二倍线的半长时，就认为出现了异常的焦点。直接拉回来，使用常规的平行点即可
          let _connect_l = last_4th.toVec2();
          if (connect_l) {
            if (Vec2.distance(connect_l, t_current.toVec2()) < this._curve_height / 2 * 1.414) {
              _connect_l = connect_l;
            }
          }
          let _connect_r = first_vertical_pos;
          if (connect_r) {
            if (Vec2.distance(connect_r, t_current.toVec2()) < this._curve_height / 2 * 1.414) {
              _connect_r = connect_r;
            }
          }

          this._vertexes.push(_connect_l.toVec3());
          this._vertexes.push(_connect_r.toVec3());
          this._vertexes.push(fourth_vertical_pos.toVec3());
          this._vertexes.push(second_vertical_pos.toVec3());
        }

        /*
        let connect_pos = v2(0, 0);
        // this._vertexes.push(A.sub(v2(0,this._curve_width)));
        //要在两段的连接处加入一个点,在内凹时加入这个点,在外凸时删除前后两个点,只保留这个点. 'Add a point at the connection of two segments, add this point when concave inward, delete the two points before and after when convex outward, and keep only this point.'
        const bConnect = index > 0; //是否有连接点 'Whether there is a connection point'
        let bInner: boolean = false; //是否内凹 'Whether it is concave inward'
        let t_pre = temp[index - 1];
        if (bConnect) {
          let t_center = t_next.clone().add(t_pre).multiplyScalar(0.5);
          bInner = t_center.y > t_current.y;
          if (index == 1) { 
            //log('第一个连接点类型为:'+(bInner?"内凹":"外凸")); 'The type of the first connection point is:'+(bInner?"Concave inward":"Convex outward")
          }
          if (bInner) {
            const v1 = t_current.clone().subtract(t_pre);
            const v2 = t_next.clone().subtract(t_current);
            const angle1 = this._signedAngle3D(v1, v2);
            connect_pos = first_vertical_pos
              .clone()
              .subtract(t_current.toVec2())
              .rotate((angle1 / 2) * -1.0)
              .add(t_current.toVec2());

            if (index == 1) {
            }
          } else {
            const v1 = t_current.clone().subtract(t_pre);
            const v2 = t_next.clone().subtract(t_current);
            const angle1 = Math.acos(v1.dot(v2) / (v1.length() * v2.length()));
            connect_pos = first_vertical_pos
              .clone()
              .subtract(t_current.toVec2())
              .rotate((angle1 / 2) * -1.0)
              .add(t_current.toVec2());

            if (index == 1) {
            }
          }
        }

        if (index === 0) {
          // this._vertexes.push(t_current);
          this._vertexes.push(third_vertical_pos.toVec3());
          this._vertexes.push(first_vertical_pos.toVec3());
          // this._vertexes.push(t_next);
          this._vertexes.push(fourth_vertical_pos.toVec3());
          this._vertexes.push(second_vertical_pos.toVec3());
        } else {
          if (bInner) {
            // this._vertexes.push(t_current);
            this._vertexes.push(third_vertical_pos.toVec3());
            this._vertexes.push(connect_pos.toVec3());
            // this._vertexes.push(t_current);
            this._vertexes.push(third_vertical_pos.toVec3());
            this._vertexes.push(first_vertical_pos.toVec3());
            // this._vertexes.push(t_next);
            this._vertexes.push(fourth_vertical_pos.toVec3());
            this._vertexes.push(second_vertical_pos.toVec3());
          } else {
            let p2 = this._vertexes.pop() as Vec3;
            // let p1 = t_pre.clone().subtract(t_current).add(p2);
            let p1 = t_pre.clone().subtract(third_vertical_pos.toVec3()).add(p2);
            let p3 = first_vertical_pos;
            let p4 = second_vertical_pos;
            let crossPoint = this._crossPointBy2Segment(p1.toVec2(), p2.toVec2(), p3, p4);

            if (crossPoint) {
              this._vertexes.push(crossPoint.toVec3());

              if (index == 1) {
              }
            } else {
              this._vertexes.push(p2);
            }

            // this._vertexes.push(t_next);
            this._vertexes.push(fourth_vertical_pos.toVec3());
            this._vertexes.push(second_vertical_pos.toVec3());
          }
        }
        */
      }
    }
    // console.log(this._vertexes);

    this._vertexes.forEach((item) => {
      positions.push(item.x, item.y, item.z);
    });

    return positions;
  }

  //计算正常竖直模式的uv 'Calculate UVs for normal vertical mode'
  private _calculateUVs() {
    let uvs: number[] = [];
    if (!this._spFrame || this._vertexes.length === 0 || this._vertexesSegment.length < 2) {
      return uvs;
    }

    for (let i = 0; i < this._vertexes.length; i++) {
      const d = this._vertexes[i];
      const u = d.x / this._curve_width;
      let v = 1.0 - d.y / this._curve_height;
      if (this._updownFix) {
        v = d.y / this._curve_height;
      }
      uvs.push(u, v);
    }

    return uvs;
  }

  private _calculateTangentsUVs() {
    let uvs: number[] = [];
    const textureWidth = this._curve_width;
    const textureSegmentLength = textureWidth;

    let index = 0;
    let last_pos_up: Vec3 | null = null;
    let length_all_up = 0;
    let current_top_u = 0;

    for (const pt of this._vertexes) {
      let u = 0;
      let v = 0;

      if (index % 2 == 0) {
        v = this._updownFix ? 1 : 0;
        if (last_pos_up != null) {
          length_all_up += pt.clone().subtract(last_pos_up).length();
          // 直接计算重复的纹理坐标
          u = length_all_up / textureSegmentLength;
        } else {
          u = 0;
        }
        current_top_u = u;
        last_pos_up = pt;
      } else {
        v = this._updownFix ? 0 : 1;
        u = current_top_u;
      }

      uvs.push(u, v);
      index += 1;
    }

    return uvs;
  }

  private _calculateNormals() {
    let normals: number[] = [];
    const normal = v3(0, 0, 1);
    for (let i = 0; i < this._vertexes.length; i++) {
      normals.push(normal.x, normal.y, normal.z);
    }
    return normals;
  }

  private _calculateIndices() {
    const indices: number[] = [];
    const vertexCount = this._vertexes.length;
    for (let i = 0; i < vertexCount - 2; i++) {
      if (this.node.scale.x * this.node.scale.y > 0) {
        indices.push(i);
        indices.push(i % 2 == 0 ? i + 1 : i + 2);
        indices.push(i % 2 == 0 ? i + 2 : i + 1);
      } else {
        indices.push(i);
        indices.push(i % 2 == 0 ? i + 2 : i + 1);
        indices.push(i % 2 == 0 ? i + 1 : i + 2);
      }
    }
    return indices;
  }

  //-------------------提取物理多边形点------------------------ 'Extract physical polygon points'

  private _extractPolygonOutline(indata: Vec3[]) {
    if (this._renderMode == TerrainRenderMode.VERTICAL_MODE) {
      const points: Vec2[] = [];
      let d = this._vertexesSegment.map((item) => item.clone().add(this._offsetCollider).toVec2());
      points.push(...d);
      let down = d.map((item) => item.clone().add(v2(0, -this._curve_collider_thickness))).reverse();
      points.push(...down);
      return points;
    }
    const up: Vec2[] = [];
    const down: Vec2[] = [];
    const n = this._vertexesSegment.length;
    if (n < 2) return [];

    for (let i = 0; i < n; i++) {
      const cur = this._vertexesSegment[i].clone(); //.add(this._offsetCollider);
      // 计算切线 'Calculate tangent'
      let tangent: Vec3;
      if (i === 0) {
        tangent = this._vertexesSegment[1].clone().subtract(this._vertexesSegment[0]);
      } else if (i === n - 1) {
        tangent = this._vertexesSegment[n - 1].clone().subtract(this._vertexesSegment[n - 2]);
      } else {
        tangent = this._vertexesSegment[i + 1].clone().subtract(this._vertexesSegment[i - 1]);
      }
      // 防止零向量 'Prevent zero vector'
      if (tangent.length() < 1e-6) {
        tangent = v3(0, 1, 0); // 默认向上 'Default upward'
      } else {
        tangent = tangent.normalize();
      }
      // 法线 'Normal'
      let normal = v3(tangent.y, -tangent.x, 0);
      if (normal.length() < 1e-6) {
        normal = v3(1, 0, 0); // 默认向右 'Default rightward'
      } else {
        normal = normal.normalize();
      }
      //上边界处理 _offsetCollider.x ,切线模式对x的offset预期是正常向右 'Upper boundary processing _offsetCollider.x, tangent mode expects the x offset to be normal rightward'
      let upOffsetX = cur.clone().add(v3(this._offsetCollider.x, 0, 0));
      //上边界处理 _offsetCollider y ,切线模式对y的offset预期是沿法线向下 'Upper boundary processing _offsetCollider.y, tangent mode expects the y offset to be downward along the normal'
      let upOffsetResult = upOffsetX.clone().add(normal.clone().multiplyScalar(-this._offsetCollider.y));

      up.push(v2(upOffsetResult.x, upOffsetResult.y));
      const downPt = upOffsetResult.clone().add(normal.clone().multiplyScalar(this._curve_collider_thickness));
      down.push(v2(downPt.x, downPt.y));
    }
    let r = up.concat(down.reverse());

    for (let i = r.length - 1; i > 0; i--) {
      // if (r[i].equals(r[i - 1], 0.01)) {
      if (isCloseToVec2(r[i], r[i - 1], 2)) {
        r.splice(i, 1);
      }
    }

    r = r.filter((v) => isFinite(v.x) && isFinite(v.y) && (v.x !== 0 || v.y !== 0));

    return r;
  }

  private _NAME_OF_PHYSICSNODE: string = 'physicContainerNode';
  //同步更新物理组件数据 'Synchronize and update physical component data'
  private generatePhysicData() {
    if (!this._physcisOnOff) {
      let pnode = this.node.getChildByName(this._NAME_OF_PHYSICSNODE);
      if (pnode) {
        pnode.destroy();
      }
      return;
    }
    //将物理组件挂在一个指定的子节点上,保证这个子节点为第一个.
    let pnode = this.node.getChildByName(this._NAME_OF_PHYSICSNODE);
    if (!pnode) {
      pnode = new Node(this._NAME_OF_PHYSICSNODE);
      pnode.setSiblingIndex(0);
      this.node.addChild(pnode);
    }
    // pnode.x = 0;
    // pnode.y = 0;
    pnode.setPosition(0, 0, 0);

    this._physicsCollider = pnode.getComponent(PolygonCollider2D);
    if (!this._physicsCollider) {
      this._physicsCollider = pnode.addComponent(PolygonCollider2D);
    }
    const rigid = pnode.getComponent(RigidBody2D);
    if (!rigid) {
      const rcomp = pnode.addComponent(RigidBody2D);
      rcomp.type = ERigidBody2DType.Static;
    }

    // const indices = this._calculateIndices();
    const polygonPoints = this._extractPolygonOutline(this._vertexes);
    this._physicsCollider.points = polygonPoints;
    this._physicsCollider.apply();
  }

  //---------------------- 编辑器中编辑地图部分,运行时无效--------------- 'Edit map in editor, not valid during runtime'

  @property({})
  private _visibleOnOff: boolean = true;
  @property({
    type: CCBoolean,
    tooltip: IN_EDITOR_ZH ? '控制点显示/隐藏' : ' (Whether to display gizmo in editor)',
    displayName: IN_EDITOR_ZH ? '控制点显示/隐藏' : ' (Whether to display gizmo in editor)',
    group: IN_EDITOR_ZH ? '控制点属性' : ' (Terrain Editing Parameter Control)',
  })
  get gizmoVisibleOnOff() {
    return this._visibleOnOff;
  }
  set gizmoVisibleOnOff(value) {
    this._editNodes.forEach((item) => {
      item.active = value;
    });
    this._visibleOnOff = value;
  }

  @property({})
  private _sortInXDirection: boolean = true;
  @property({
    type: CCBoolean,
    tooltip: IN_EDITOR_ZH ? '是否在X轴方向排序' : ' (Sort in X direction)',
    displayName: IN_EDITOR_ZH ? '是否在X轴方向排序' : ' (Sort in X direction)',
    group: IN_EDITOR_ZH ? '控制点属性' : ' (Terrain Editing Parameter Control)',
  })
  get sortInXDirection() {
    return this._sortInXDirection;
  }
  set sortInXDirection(value) {
    this._sortInXDirection = value;
  }

  @property({})
  private _editWhenRun: boolean = false;
  @property({
    type: CCBoolean,
    tooltip: IN_EDITOR_ZH ? '运行时存在/不存在' : 'editWhenRun',
    displayName: IN_EDITOR_ZH ? '运行时存在/不存在' : 'editWhenRun',
    group: IN_EDITOR_ZH ? '控制点属性' : ' (Terrain Editing Parameter Control)',
  })
  get editWhenRun() {
    return this._editWhenRun;
  }
  set editWhenRun(value) {
    this._editWhenRun = value;
    // this._refreshAll();
  }

  private _registerKeyForGizmo() {
    // if (!EDITOR) return;
    // input.on(Input.EventType.KEY_DOWN, (event: EventKeyboard) => {
    //   if (event.keyCode === KeyCode.SPACE) {
    //     this._showOnOff = false;
    //     this.showOnOff = !this.showOnOff;
    //   }
    // });
  }

  @property({ type: SpriteFrame })
  private _gizmotag: SpriteFrame | null = null;
  @property({
    type: SpriteFrame,
    tooltip: IN_EDITOR_ZH ? '控制点纹理' : ' (Texture of Terrain Control Points)',
    displayName: IN_EDITOR_ZH ? '控制点纹理' : ' (Texture of Terrain Control Points)',
    group: {
      name: IN_EDITOR_ZH ? '控制点属性' : ' (Terrain Editing Parameter Control)',
      style: GROUP_STYLE_NAME,
    },
  })
  get gizmoTag() {
    return this._gizmotag;
  }
  set gizmoTag(value) {
    this._gizmotag = value;
    // this._refreshAll();
  }

  @property({})
  private _gizmoColor: Color = new Color('#9EECFF');
  @property({
    type: Color,
    tooltip: IN_EDITOR_ZH
      ? '编辑控制点时的gizmo颜色,alpha值最小值为50'
      : ' (Gizmo color when editing control points, alpha value is >=50)',
    displayName: IN_EDITOR_ZH ? '控制点颜色' : ' (Color of  Control Points)',
    group: IN_EDITOR_ZH ? '控制点属性' : ' (Points Editing Parameter Control)',
  })
  get gizmoColor() {
    return this._gizmoColor;
  }
  set gizmoColor(value) {
    let a = value.a < 50 ? 50 : value.a;
    a = value.a > 200 ? 200 : value.a;
    // console.log('gizmoColor', this._gizmoColor);
    this._gizmoColor.set(value.r, value.g, value.b, a);
    // this._refreshAll();
  }

  // @property({ type: SpriteFrame })
  // private _gizmotagline: SpriteFrame | null = null;
  // @property({
  //   type: SpriteFrame,
  //   tooltip: IN_EDITOR_ZH ? '控制点连接纹理' : ' (Texture of  Control Lines)',
  //   displayName: IN_EDITOR_ZH ? '控制点连接纹理' : ' (Texture of Control Lines)',
  //   group: {
  //     name: IN_EDITOR_ZH ? '控制点属性' : ' (Terrain Editing Parameter Control)',
  //     style: GROUP_STYLE_NAME,
  //   },
  // })
  // get gizmoTagLine() {
  //   return this._gizmotagline;
  // }
  // set gizmoTagLine(value) {
  //   this._gizmotagline = value;
  //   // this._refreshAll();
  // }

  private _editNodes: Node[] = [];
  private _lastPositionHash: string = '';
  private _lastSyncPositionHash: string = '';
  private _padStart(str: string, targetLength: number, padString: string = '0'): string {
    while (str.length < targetLength) {
      str = padString + str;
    }
    return str;
  }
  private _simpleHash(s: string, len: number = 8): string {
    let hash = 5381;
    for (let i = 0; i < s.length; i++) {
      hash = (hash << 5) + hash + s.charCodeAt(i);
      hash = hash & 0xffffffff;
    }

    let hex = (hash >>> 0).toString(16);
    return this._padStart(hex, len).slice(-len);
  }
  private _normalizeAngle(angle: number): number {
    return ((angle % 360) + 360) % 360; // 将角度转化为0-360之间的值 'Convert angle to a value between 0-360'
  }
  private _angleOfVec(vin: Vec2) {
    if (!vin) return 0;
    if (vin.x == 0 && vin.y == 0) return 0;
    let ve2 = v2(vin.x, vin.y);
    let a1 = misc.radiansToDegrees(ve2.signAngle(v2(1, 0)));
    return this._normalizeAngle(-a1);
  }

  //将AssetManager.instance.loadBundle('xxx');封装为一个同步函数,使用的时候可以await调用 'Encapsulate AssetManager.instance.loadBundle('xxx') as a synchronous function, can be called with await'
  // private async loadBundleSync(bundleName: string): Promise<AssetManager.Bundle> {
  //   return new Promise((resolve, reject) => {
  //     AssetManager.instance.loadBundle(bundleName, (err, bundle) => {
  //       if (err) {
  //         console.warn('loadBundleSync error: ', err);
  //         reject(err);
  //       } else {
  //         resolve(bundle);
  //       }
  //     });
  //   });
  // }

  // private async loadResSync(bundle: AssetManager.Bundle, path: string): Promise<Asset> {
  //   return new Promise((resolve, reject) => {
  //     bundle.load(path, (err, res) => {
  //       if (err) {
  //         console.warn('loadResSync error: ', err);
  //         reject(err);
  //       } else {
  //         resolve(res);
  //       }
  //     });
  //   });
  // }

  protected async update(dt: number): Promise<void> {
    //跟随模式下统一处理数据 'Unified data processing in follow mode'
    if (this._syncOnOff && this._syncTarget) {
      const ct = this._syncTarget.getComponent(CurveTexture);
      this._controlPoints = ct!.controlPoints;
      this._vertexesSegment = ct!.vertexesSegment;
      this._updateHeadTail();
      this.node.setPosition(this._syncTarget.position.x, this._syncTarget.position.y);
      if (this.node.getScale().x !== this._syncTarget.scale.x || this.node.getScale().y !== this._syncTarget.scale.y) {
        this.node.setScale(this._syncTarget.scale.x, this._syncTarget.scale.y, 1);
      }
      // this.node.z = this._syncTarget.z;

      let str = '';
      this._controlPoints.forEach((item) => {
        str += `${item.x},${item.y},${item.z};`;
      });
      let hash = this._simpleHash(str);
      //有数据变动才刷新 'Refresh only when there is data change'
      if (hash != this._lastSyncPositionHash) {
        this._lastSyncPositionHash = hash;
        this._refreshAll('1683');
      }

      //清除控制点 'Clear control points'
      this._editNodes = this.node.children.filter((item) => item.name.startsWith('gizmo_'));
      this._editNodes.forEach((item) => {
        item.destroy();
      });
      return;
    }

    if (this._gizmotag == null) {
      // console.warn('需要提前指定gizmo的资源! 拖入 gizmoTag 资源即可' +
      // ' (Gizmo resources need to be specified in advance! Drag in gizmoTag resources)');
      return;
    }

    // if (this._gizmotagline == null) {
    //   return;
    // }

    this._editNodes = this.node.children.filter((item) => item.name.startsWith('gizmo_'));
    if (!this._editWhenRun && !EDITOR_NOT_IN_PREVIEW) {
      this._editNodes.forEach((element) => {
        element.destroy();
      });
      return;
    }

    //初始自动添加编辑控制节点 'Initially automatically add editing control nodes'


    if (!this.manualControlPoints) {
      if (this._editNodes.length <= 0) {
        const cpcounts = this._controlPoints.length;
        for (let m = 0; m < cpcounts; m++) {
          // console.log('新添加了一个控制点');
          let pos = this._controlPoints[m];
          const n = new Node();
          n.name = 'gizmo_' + (m + 1);
          n.setPosition(pos.x, pos.y, pos.z);
          const opa = n.addComponent(UIOpacity);
          let uit = n.addComponent(UITransform);
          const spc = n.addComponent(Sprite);
          this.node.addChild(n);
          spc.spriteFrame = this._gizmotag;
          uit.setContentSize(80, 80);
          const item = n;
          let sp = item.getComponent(Sprite);
          sp.color = this.gizmoColor;
          if (this._editWhenRun) {
            n.addComponent(MoveWithTouch);
          }
        }
      }
      //实时控制节点显示效果 'Real-time control node display effect'
      for (let i = 0; i < this._editNodes.length; i++) {
        const item = this._editNodes[i];
        let uit = item.getComponent(UITransform);
        uit.setContentSize(80, 80);
        let opa = item.getComponent(UIOpacity);
        opa.opacity = 255;
        let sp = item.getComponent(Sprite);
        sp.color = this.gizmoColor;
        sp.spriteFrame = this._gizmotag;
        if (this._editWhenRun) {
          if (item.getComponent(MoveWithTouch) == null) {
            item.addComponent(MoveWithTouch);
          }
        } else {
          const mtouch = item.getComponent(MoveWithTouch);
          if (mtouch) {
            mtouch.destroy();
          }
        }
      }
    }

    if (this._sortInXDirection) {
      //将所有子节点按照X增序排列 'Sort all child nodes in ascending order of X'
      this._editNodes.sort((a, b) => {
        const posA = a.getPosition();
        const posB = b.getPosition();
        return posA.x - posB.x;
      });
    }

    if (EDITOR) {
      for (let i = 1; i < this._editNodes.length; i++) {
        const pre = this._editNodes[i - 1];
        const cur = this._editNodes[i];
        const prePos = pre.getPosition();
        const curPos = cur.getPosition();
        // if (prePos.x == curPos.x) {
        //   cur.setPosition(curPos.x + 120, curPos.y, curPos.z);
        // }
        cur.setPosition(curPos.x, curPos.y, curPos.z);
      }
    }

    for (let j = 0; j < this._editNodes.length; j++) {
      const cur = this._editNodes[j];
      if (j == this._editNodes.length - 1) {
        cur.angle = 0;
        break;
      }
      const next = this._editNodes[j + 1];
      const curPos = cur.getPosition();
      const nextPos = next.getPosition();
      const ang = this._angleOfVec(nextPos.clone().subtract(curPos).toVec2()); // - 90;
      cur.angle = ang;
    }

    if (this._sortInXDirection) {
      //再次按照x增序排列 'Sort again in ascending order of x'
      this._editNodes.sort((a, b) => {
        const posA = a.getPosition();
        const posB = b.getPosition();
        return posA.x - posB.x;
      });
    }

    let str = '';
    this._editNodes.forEach((item) => {
      const pos = item.getPosition();
      str += `${pos.x},${pos.y},${pos.z};`;
    });
    let hash = this._simpleHash(str);
    if (hash !== this._lastPositionHash) {
      this._lastPositionHash = hash;
      if (!this.manualControlPoints) {
        this._controlPoints.length = 0;
        this._editNodes.forEach((item) => {
          const pos = item.getPosition();
          if (this.reverseHeadTail) {
            this._controlPoints.unshift(pos);
          } else {
            this._controlPoints.push(pos);
          }
        });
      }
      this._refreshAll('1567');
    }
  }
}
